宏
宏在C语言里及其重要,而在C++里用得就少多了。关于宏的第一规则是:绝不应该去使用它,除非你不得不这样做。几乎每个宏都表明了程序设计语言里,或者程序里,或者程序员的一个缺陷,因为它将在编译器看到程序的正文之前去重新摆布这些正文。宏也是许多程序设计工具的主要麻烦。所以,如果你使用了宏,你就应该准备着只能从各种工具(如排错系统、交叉引用工具、轮廓程序等)得到较少的服务。如果你必须使用宏,那么请仔细阅读你所用的C++预处理器实现的手册,而且不用过于自作聪明。还要警告读者,应该按照习惯,在为宏命名时使用许多大写字母。宏的语法在A.11节给出。
简单的宏定义就像下面这样:
#define NAME rest of line
当NAME作为一个单词被遇到时,它就会被用字符序列rest of line(该行剩下的部分)取代。
例如,
named = NAME
将被展开为
named = rest of line
页可以定义带参数的宏,例如,
#define MAC(x, y) argument1:x argument2:y
在使用MAC时必须提供两个字符串,它们将在MAC()被展开时用于取代x和y,例如,
expanded = MAC(foo bar, yuk yuk)
将展开成
expanded = argument1:foo bar argument2:yuk yuk
宏名字不能重载,而且宏预处理器不能处理递归调用:
#define PRINT(a, b) cout << (a) << (b)
#define PRINT(a, b, c) cout << (a) << (b) << (c) /* 麻烦? : 重复定义,不允许重载 */
#define FAC(n) (n > 1) ? n * FAC(n - 1) : 1 /* 麻烦:递归的宏 */
宏对字符串进行操作,它对C++的语法知之甚少,根本不知道C++的类型和作用域规则。编译器能看到的只是宏展开后的形式,所以在宏中的错误是在宏被展开之后的报告的,而不是在它定义时,这可能导致非常难以理解的错误信息。
下面是一些可能有用的宏
#define CASE break;case
#define FOREVER for(;;)
下面是一些完全没有必要的宏:
#define PI 3.141593
#define BEGIN {
#define END }
下面是一些很危险的宏:
#define SQUARE(a) a*a
#define INCR_xx(xx)++
想知道它们为什么危险,请试着展开这个程序片段:
int xx = 0; // 全局计数器
void f()
{
int xx = 0; // 局部变量
int y = SQUARE(xx+2); // y = xx + 2 * xx + 2; 即 y = xx + (2*xx) + 2
INCR_xx; // 增加局部变量xx
}
如果你要使用宏,在引用全局名字时一定要使用作用域解析运算符::(4.9.4节),并在所有可能的地方将出现的宏参数都用括号括起来。例如,
#define MIN(a, b) (((a)<(b)) ? (a) : (b))
如果你写的宏足够复杂,需要写注释时,采用/* */形式的注释是比较明智的,因为有时会有不懂C++的 // 注释的C预处理程序被用做C++工具的一部分。例如,
#define M2(a) something(a) /* 细心的注释 */
通过利用宏,你可以设计出自己的私有语言。即使与C++相比你更偏爱自己的这种“增强的语言”,但对于大部分C++程序员而言,它也是不可理解的。进一步说,C预处理器是一种很简单的宏处理器。当你试图去做某些不那么简单的事情时,你多半会发现不能做好,或者做起来有不必要的困难。const、inline、template、enum和namespace机制等都是为了用做预处理器机构的许多传统使用方式的替代品。例如,
const int answer = 42;
template <class T> inline T min(T a, T b) { return (a < b) ? a : b; }
在写宏的时候,需要为某些东西提供新名字也是很常见的事情。通过##宏运算符可以拼接起两个串,构造出一个新串。例如,
#define NAME2(a,b) a##b
int NAME2(hack,cah)();
将产生
int hackcah();
提供给编译器去读。
指令
#undef X
保证不再有称为X的有定义的宏---无论在此指令之前有还是没有。这种东西可以用于防止某些不想要的宏。然而,要想知道X对一片代码的作用是否如自己所设想的那样,那可不总是一件容易的事情。
🔚